package com.qnzw.zjmy.utils.risk;


import android.net.LocalServerSocket;
import android.util.Log;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.BindException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;

import cn.hutool.core.util.RandomUtil;

/**
 * Project Name:checkMultiApk
 * Package Name:com.lahm.library
 * Created by lahm on 2018/5/14 下午4:11
 */
public class VirtualApkCheckUtil {
    private String TAG = "test";
    private static volatile VirtualApkCheckUtil singleInstance;

    private VirtualApkCheckUtil() {
    }

    public static VirtualApkCheckUtil getSingleInstance() {
        if (singleInstance == null) {
            synchronized (VirtualApkCheckUtil.class) {
                if (singleInstance == null) {
                    singleInstance = new VirtualApkCheckUtil();
                }
            }
        }
        return singleInstance;
    }


    /**
     * 端口监听，先扫一遍已开启的端口并连接，
     * 如果发现能通信且通信信息一致，
     * 则认为之前有一个相同的自己打开了（也就是被多开了）
     * 如果没有，则开启监听
     * 这个方法没有 checkByCreateLocalServerSocket 方法简单，不推荐使用
     *
     * @param secret
     * @param callback
     */
    public void checkByPortListening(String secret, String usId, VirtualCheckCallback callback) {
        startClient(secret + "," + usId);
        new ServerThread(secret, callback).start();
    }

    //此时app作为secret的接收方，也就是server角色
    private class ServerThread extends Thread {
        String secret;
        VirtualCheckCallback callback;

        private ServerThread(String secret, VirtualCheckCallback callback) {
            this.secret = secret;
            this.callback = callback;
        }

        @Override
        public void run() {
            super.run();
            startServer(secret, callback);
        }
    }

    //找一个没被占用的端口开启监听
    //如果监听到有连接，开启读线程
    private void startServer(String secret, VirtualCheckCallback callback) {
        int prod = RandomUtil.randomInt(10000, 10010);
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress("127.0.0.1", prod));
            while (true) {
                Socket socket = serverSocket.accept();
                ReadThread readThread = new ReadThread(secret, socket, callback);
                readThread.start();
                serverSocket.close();
            }
        } catch (BindException e) {
            startServer(secret, callback);//may be loop forever
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //读线程读流信息，如果包含secret则认为被广义多开
    private class ReadThread extends Thread {
        private ReadThread(String secret, Socket socket, VirtualCheckCallback callback) {
            InputStream inputStream = null;
            try {
                inputStream = socket.getInputStream();
                byte buffer[] = new byte[1024 * 4];
                int temp = 0;
                while ((temp = inputStream.read(buffer)) != -1) {
                    String result = new String(buffer, 0, temp);
                    if (result.contains(secret) && callback != null) callback.findSuspect(result);
                }
                inputStream.close();
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //读文件扫描已开启的端口，放入端口列表，每个端口都尝试连接一次
    private void startClient(String secret) {
        for (int i = 10000; i < 10010; i++) {
            new ClientThread(secret, i).start();
        }
    }

    //app此时作为secret的发送方（也就是client角色），发送完毕就结束
    private class ClientThread extends Thread {
        String secret;
        int port;

        private ClientThread(String secret, int port) {
            this.secret = secret;
            this.port = port;
        }

        @Override
        public void run() {
            super.run();
            try {
                Socket socket = new Socket("127.0.0.1", port);
                socket.setSoTimeout(2000);
                OutputStream outputStream = socket.getOutputStream();
                outputStream.write((secret).getBytes("utf-8"));
                outputStream.flush();
                socket.shutdownOutput();

                InputStream inputStream = socket.getInputStream();
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                String info = null;
                while ((info = bufferedReader.readLine()) != null) {
                    Log.i(TAG, "ClientThread: " + info);
                }
                bufferedReader.close();
                inputStream.close();
                socket.close();
            } catch (ConnectException e) {
                Log.i(TAG, port + "port refused");
            } catch (SocketException e) {
                e.printStackTrace();
            } catch (UnknownHostException e) {
                e.printStackTrace();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 如issue25讨论
     * https://github.com/lamster2018/EasyProtector/issues/25
     * 感谢https://github.com/wangkunlin提供
     *
     * @param uniqueMsg
     * @param callback
     * @return
     */
    private volatile LocalServerSocket localServerSocket;

    /**
     * @param uniqueMsg 不要使用固定值，多个马甲包或多进程时会误报。
     *                  如果是单进程使用，推荐使用context.getPackageName()；
     *                  如果是多进程，推荐使用进程名{@link ()}
     * @return
     */
    public boolean checkByCreateLocalServerSocket(String uniqueMsg) {
        if (localServerSocket != null) return false;
        try {
            localServerSocket = new LocalServerSocket(uniqueMsg);
            return false;
        } catch (IOException e) {
            return true;
        }
    }

}
